package com.lc1993929.shucao.util;

import com.lc1993929.shucao.constants.Constants;
import com.lc1993929.shucao.entity.PosInfo;
import com.lc1993929.shucao.enums.CanNotInEnum;
import com.lc1993929.shucao.enums.Direct;
import com.lc1993929.shucao.map.Coord;
import com.lc1993929.shucao.map.Node;

import java.util.*;

public class AStar {

    public final static int DIRECT_VALUE = 1; // 横竖移动代价

    // 优先队列(升序)
    private static Queue<Node> openList = new PriorityQueue<Node>();
    private static List<Node> closeList = new ArrayList<Node>();

    public static Direct getDirect(Coord start, Coord end, CanNotInEnum canNotInEnum) {
        if (!canMove(start, canNotInEnum)) {
            return Direct.STAY;
        }

        if (start.equals(end)) {
            return Direct.STAY;
        }

        Coord nextCoord = findNextCoord(start, end, canNotInEnum);
        if (Objects.isNull(nextCoord)) {
            return MapUtil.getRandomDirection();
        }
        return findDirect(nextCoord, start);
    }

    private static boolean canMove(Coord start, CanNotInEnum canNotInEnum) {
        return canMoveTo(new Coord(start.getX() - 1, start.getY()), canNotInEnum)
                || canMoveTo(new Coord(start.getX() + 1, start.getY()), canNotInEnum)
                || canMoveTo(new Coord(start.getX(), start.getY() - 1), canNotInEnum)
                || canMoveTo(new Coord(start.getX(), start.getY() + 1), canNotInEnum);
    }

    private static boolean canMoveTo(Coord coord, CanNotInEnum canNotInEnum) {
        // 是否在地图中
        if (coord.getX() < 0 || coord.getX() >= Constants.playerMap.getColLen() || coord.getY() < 0 || coord.getY() >= Constants.playerMap.getRowLen()) {
            return false;
        }

        PosInfo posInfo = MapUtil.getPosInfoByCoord(coord);
        if (posInfo == null || posInfo.getBlock()) {
            return false;
        }

        //抢夺策略时不要与友军重叠
        if (canNotInEnum == CanNotInEnum.Friend && havePlayer(coord)) {
            return false;
        } else if (canNotInEnum == CanNotInEnum.Enemy && haveEnemy(coord)) {
            //逃跑策略时不要与敌军重叠
            return false;
        } else if (canNotInEnum == CanNotInEnum.EnemyAndNear) {
            if (nearEnemy(coord)) {
                return false;
            }
        }
        return true;
    }


    /**
     * 根据起点和下一个目标节点选出前进方向
     *
     * @param nextCoord
     * @param start
     * @return
     */
    private static Direct findDirect(Coord nextCoord, Coord start) {
        if (nextCoord.getX() > start.getX()) {
            return Direct.RIGHT;
        } else if (nextCoord.getX() < start.getX()) {
            return Direct.LEFT;
        } else if (nextCoord.getY() > start.getY()) {
            return Direct.DOWN;
        } else if (nextCoord.getY() < start.getY()) {
            return Direct.UP;
        } else {
            return Direct.STAY;
        }
    }

    private static Coord findNextCoord(Coord start, Coord end, CanNotInEnum canNotInEnum) {
        Node startNode = new Node(start);
        openList.clear();
        closeList.clear();

        openList.add(startNode);

        Node nextNode = findNextNode(end, canNotInEnum);

        if (Objects.isNull(nextNode)) {
            return null;
        }

        return nextNode.getCoord();
    }

    /**
     * 判断目标点是否可达
     *
     * @param start
     * @param endCoord
     * @param canNotInEnum
     * @return
     */
    public static boolean canArrive(Coord start, Coord endCoord, CanNotInEnum canNotInEnum) {
        Node startNode = new Node(start);
        openList.clear();
        closeList.clear();

        openList.add(startNode);

        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            Node endNode = findNodeInClose(endCoord);
            if (endNode != null) {
                return true;
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(current, endCoord, canNotInEnum);
        }
        return false;
    }

    private static Node findNextNode(Coord endCoord, CanNotInEnum canNotInEnum) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            Node endNode = findNodeInClose(endCoord);
            if (endNode != null) {
                return getNextNode(endNode);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(current, endCoord, canNotInEnum);
        }

        //如果目标点是不可到达的，则找出离目标点最近的一个点前进
        Optional<Node> optional = closeList.stream().filter(node -> node.getH() > 0).min(Comparator.comparingInt(Node::getH));
        if (!optional.isPresent()) {
            return null;
        }

        Node end = optional.get();
        return getNextNode(end);
    }

    private static void addNeighborNodeInOpen(Node current, Coord endCoord, CanNotInEnum canNotInEnum) {
        // 左
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX() - 1, current.getCoord().getY()), endCoord, canNotInEnum);
        // 上
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX(), current.getCoord().getY() - 1), endCoord, canNotInEnum);
        // 右
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX() + 1, current.getCoord().getY()), endCoord, canNotInEnum);
        // 下
        addNeighborNodeInOpen(current, new Coord(current.getCoord().getX(), current.getCoord().getY() + 1), endCoord, canNotInEnum);
    }

    private static void addNeighborNodeInOpen(Node current, Coord neighborCoord, Coord endCoord, CanNotInEnum canNotInEnum) {
        //如果该点无法加入到open中，则直接忽略
        if (canAddNodeToOpen(neighborCoord, canNotInEnum)) {
            // 计算邻结点的G值
            int G = current.getG() + DIRECT_VALUE;
            //在open中找到邻结点
            Node child = findNodeInOpen(neighborCoord);
            if (child == null) {
                // 计算H值
                int H = calcH(endCoord, neighborCoord);
                child = new Node(neighborCoord, current, G, H);
                if (neighborCoord.equals(endCoord)) {
                    closeList.add(child);
                }
                openList.add(child);
            } else if (child.getG() > G) {
                child.setG(G);
                child.setParent(current);
                openList.add(child);
            }
        }
    }


    /**
     * 判断结点能否放入Open列表
     */
    private static boolean canAddNodeToOpen(Coord coord, CanNotInEnum canNotInEnum) {
        // 是否在地图中
        if (coord.getX() < 0 || coord.getX() >= Constants.playerMap.getColLen() || coord.getY() < 0 || coord.getY() >= Constants.playerMap.getRowLen()) {
            return false;
        }

        PosInfo posInfo = MapUtil.getPosInfoByCoord(coord);
        if (posInfo == null || posInfo.getBlock()) {
            return false;
        }

        //抢夺策略时不要与友军重叠
        if (canNotInEnum == CanNotInEnum.Friend && havePlayer(coord)) {
            return false;
        } else if (canNotInEnum == CanNotInEnum.Enemy && haveEnemy(coord)) {
            //逃跑策略时不要与敌军重叠
            return false;
        } else if (canNotInEnum == CanNotInEnum.EnemyAndNear) {
            if (nearEnemy(coord)) {
                return false;
            }
        }

        return !isCoordInClose(coord);
    }

    private static boolean nearEnemy(Coord coord) {
        return (Objects.nonNull(Constants.enemy1.getCoord()) && calcH(coord, Constants.enemy1.getCoord()) <= 1)
                || (Objects.nonNull(Constants.enemy2.getCoord()) && calcH(coord, Constants.enemy2.getCoord()) <= 1)
                || (Objects.nonNull(Constants.enemy3.getCoord()) && calcH(coord, Constants.enemy3.getCoord()) <= 1)
                || (Objects.nonNull(Constants.enemy4.getCoord()) && calcH(coord, Constants.enemy4.getCoord()) <= 1);
    }

    private static boolean havePlayer(Coord coord) {
        return coord.equals(Constants.player1.getCoord()) || coord.equals(Constants.player2.getCoord())
                || coord.equals(Constants.player3.getCoord()) || coord.equals(Constants.player4.getCoord());
    }

    private static boolean haveEnemy(Coord coord) {
        return coord.equals(Constants.enemy1.getCoord()) || coord.equals(Constants.enemy2.getCoord())
                || coord.equals(Constants.enemy3.getCoord()) || coord.equals(Constants.enemy4.getCoord());
    }


    /**
     * 判断坐标是否在close表中
     */
    private static boolean isCoordInClose(Coord coord) {
        if (closeList.isEmpty()) {
            return false;
        }
        return closeList.stream().anyMatch(node -> node.getCoord().equals(coord));
    }

    /**
     * 从Open列表中查找结点
     */
    private static Node findNodeInOpen(Coord coord) {
        return openList.stream().filter(node -> node.getCoord().equals(coord))
                .findAny().orElse(null);
    }

    private static Node findNodeInClose(Coord coord) {
        return closeList.stream().filter(node -> node.getCoord().equals(coord))
                .findAny().orElse(null);
    }

    /**
     * 在二维数组中绘制路径
     */
    private static Node getNextNode(Node end) {
        if (end == null) {
            return null;
        }
        while (true) {
            if (end.getParent().getParent() == null) {
                return end;
            }
            //将终点坐标的父坐标变成路径
            end = end.getParent();
        }
    }

    /**
     * 计算H的估值：“曼哈顿”法，坐标分别取差值相加
     */
    public static int calcH(Coord end, Coord coord) {
        return Math.abs(end.getX() - coord.getX())
                + Math.abs(end.getY() - coord.getY());
    }


    //    -----------------------------------------------
    //策略2

    public static Direct getDirect2(Coord start, Coord end, CanNotInEnum canNotInEnum) {
        if (!canMove(start, canNotInEnum)) {
            return Direct.STAY;
        }

        if (start.equals(end)) {
            return Direct.STAY;
        }

        Coord nextCoord = findNextCoord2(start, end, canNotInEnum);
        if (Objects.isNull(nextCoord)) {
            return MapUtil.getRandomDirection();
        }
        return findDirect(nextCoord, start);
    }

    private static Coord findNextCoord2(Coord start, Coord end, CanNotInEnum canNotInEnum) {
        Node startNode = new Node(start);
        openList.clear();
        closeList.clear();

        openList.add(startNode);

        Node nextNode = playerGetNextNode(end, canNotInEnum);
        if (Objects.isNull(nextNode)) {
            openList.clear();
            closeList.clear();

            openList.add(startNode);
            nextNode = findNextNode(end, CanNotInEnum.NoFear);
        }

        if (Objects.isNull(nextNode)) {
            return null;
        }

        return nextNode.getCoord();
    }


    private static Node playerGetNextNode(Coord endCoord, CanNotInEnum canNotInEnum) {
        while (!openList.isEmpty()) {
            //如果终点坐标在close中，则绘制路径
            Node endNode = findNodeInClose(endCoord);
            if (endNode != null) {
                return getNextNode(endNode);
            }
            //弹栈出来的第一个是目前到起点和终点之和最小的点
            Node current = openList.poll();
            //将该点加入close中
            closeList.add(current);
            //将该点附近的点加入open中
            addNeighborNodeInOpen(current, endCoord, canNotInEnum);
        }
        return null;
    }


}
